<!DOCTYPE html>
<!--
Copyright (c) 2015 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->

<link rel="import" href="/tracing/base/base64.html">
<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
<link rel="import" href="/tracing/ui/analysis/generic_object_view.html">
<link rel="import" href="/tracing/ui/base/drag_handle.html">
<link rel="import" href="/tracing/ui/base/hotkey_controller.html">
<link rel="import" href="/tracing/ui/base/info_bar.html">
<link rel="import" href="/tracing/ui/base/list_view.html">
<link rel="import" href="/tracing/ui/base/mouse_mode_selector.html">
<link rel="import" href="/tracing/ui/base/overlay.html">
<link rel="import" href="/tracing/ui/base/utils.html">
<link rel="import" href="/tracing/ui/extras/chrome/cc/display_item_list_item.html">
<link rel="import" href="/tracing/ui/extras/chrome/cc/picture_ops_list_view.html">

<template id="tr-ui-e-chrome-cc-display-item-debugger-template">
  <left-panel>
    <display-item-info>
      <header>
        <span class='title'>Display Item List</span>
        <span class='size'></span>
        <div class='export'>
          <input class='dlfilename' type='text' value='displayitemlist.json' />
          <button class='dlexport'>Export display item list</button>
        </div>
        <div class='export'>
          <input class='skpfilename' type='text' value='skpicture.skp' />
          <button class='skpexport'>Export list as SkPicture</button>
        </div>
      </header>
    </display-item-info>
  </left-panel>
  <right-panel>
    <raster-area>
      <canvas-scroller>
        <canvas></canvas>
      </canvas-scroller>
    </raster-area>
  </right-panel>
</template>

<script>
'use strict';

tr.exportTo('tr.ui.e.chrome.cc', function() {
  const THIS_DOC = document.currentScript.ownerDocument;

  /**
   * DisplayItemDebugger is a view of a DisplayItemListSnapshot for inspecting
   * a display item list and the pictures within it.
   *
   * @constructor
   */
  const DisplayItemDebugger = tr.ui.b.define(
      'tr-ui-e-chrome-cc-display-item-debugger');

  DisplayItemDebugger.prototype = {
    __proto__: HTMLDivElement.prototype,

    decorate() {
      const node = tr.ui.b.instantiateTemplate(
          '#tr-ui-e-chrome-cc-display-item-debugger-template', THIS_DOC);

      Polymer.dom(this).appendChild(node);
      this.style.flexGrow = 1;
      this.style.flexShrink = 1;
      this.style.flexBasis = 'auto';
      this.style.display = 'flex';
      this.style.minWidth = 0;

      this.pictureAsImageData_ = undefined;
      this.zoomScaleValue_ = 1;

      this.sizeInfo_ = Polymer.dom(this).querySelector('.size');
      this.rasterArea_ = Polymer.dom(this).querySelector('raster-area');
      this.rasterArea_.style.flexGrow = 1;
      this.rasterArea_.style.flexShrink = 1;
      this.rasterArea_.style.flexBasis = 'auto';
      this.rasterArea_.style.backgroundColor = '#ddd';
      this.rasterArea_.style.minHeight = '200px';
      this.rasterArea_.style.minWidth = '200px';
      this.rasterArea_.style.paddingLeft = '5px';
      this.rasterArea_.style.display = 'flex';
      this.rasterArea_.style.flexDirection = 'column';
      this.rasterCanvas_ =
          Polymer.dom(this.rasterArea_).querySelector('canvas');
      this.rasterCtx_ = this.rasterCanvas_.getContext('2d');

      const canvasScroller = Polymer.dom(this).querySelector('canvas-scroller');
      canvasScroller.style.flexGrow = 1;
      canvasScroller.style.flexShrink = 1;
      canvasScroller.style.flexBasis = 'auto';
      canvasScroller.style.minWidth = 0;
      canvasScroller.style.minHeight = 0;
      canvasScroller.style.overflow = 'auto';

      this.trackMouse_();

      this.displayItemInfo_ =
          Polymer.dom(this).querySelector('display-item-info');
      this.displayItemInfo_.addEventListener(
          'click', this.onDisplayItemInfoClick_.bind(this), false);

      this.displayItemListView_ = new tr.ui.b.ListView();
      this.displayItemListView_.addEventListener('selection-changed',
          this.onDisplayItemListSelection_.bind(this));
      Polymer.dom(this.displayItemInfo_).appendChild(this.displayItemListView_);

      this.displayListFilename_ =
          Polymer.dom(this).querySelector('.dlfilename');
      this.displayListExportButton_ =
          Polymer.dom(this).querySelector('.dlexport');
      this.displayListExportButton_.addEventListener(
          'click', this.onExportDisplayListClicked_.bind(this));

      this.skpFilename_ = Polymer.dom(this).querySelector('.skpfilename');
      this.skpExportButton_ = Polymer.dom(this).querySelector('.skpexport');
      this.skpExportButton_.addEventListener(
          'click', this.onExportSkPictureClicked_.bind(this));

      const leftPanel = Polymer.dom(this).querySelector('left-panel');
      leftPanel.style.flexGrow = 0;
      leftPanel.style.flexShrink = 0;
      leftPanel.style.flexBasis = 'auto';
      leftPanel.style.minWidth = '200px';
      leftPanel.style.overflow = 'auto';

      leftPanel.children[0].paddingTop = '2px';
      leftPanel.children[0].children[0].style.borderBottom = '1px solid #555';

      const leftPanelTitle = leftPanel.querySelector('.title');
      leftPanelTitle.style.fontWeight = 'bold';
      leftPanelTitle.style.marginLeft = '5px';
      leftPanelTitle.style.marginright = '5px';

      for (const div of leftPanel.querySelectorAll('.export')) {
        div.style.margin = '5px';
      }

      const middleDragHandle = document.createElement('tr-ui-b-drag-handle');
      middleDragHandle.style.flexGrow = 0;
      middleDragHandle.style.flexShrink = 0;
      middleDragHandle.style.flexBasis = 'auto';
      middleDragHandle.horizontal = false;
      middleDragHandle.target = leftPanel;

      const rightPanel = Polymer.dom(this).querySelector('right-panel');
      rightPanel.style.display = 'flex';
      rightPanel.style.flexGrow = 1;
      rightPanel.style.flexShrink = 1;
      rightPanel.style.flexBasis = 'auto';
      rightPanel.style.minWidth = 0;

      this.infoBar_ = document.createElement('tr-ui-b-info-bar');
      Polymer.dom(this.rasterArea_).insertBefore(this.infoBar_, canvasScroller);

      Polymer.dom(this).insertBefore(middleDragHandle, rightPanel);

      this.picture_ = undefined;

      this.pictureOpsListView_ = new tr.ui.e.chrome.cc.PictureOpsListView();
      this.pictureOpsListView_.style.flexGrow = 0;
      this.pictureOpsListView_.style.flexShrink = 0;
      this.pictureOpsListView_.style.flexBasis = 'auto';
      this.pictureOpsListView_.style.overflow = 'auto';
      this.pictureOpsListView_.style.minWidth = '100px';
      Polymer.dom(rightPanel).insertBefore(
          this.pictureOpsListView_, this.rasterArea_);

      this.pictureOpsListDragHandle_ =
          document.createElement('tr-ui-b-drag-handle');
      this.pictureOpsListDragHandle_.horizontal = false;
      this.pictureOpsListDragHandle_.target = this.pictureOpsListView_;
      Polymer.dom(rightPanel).insertBefore(
          this.pictureOpsListDragHandle_, this.rasterArea_);
    },

    get picture() {
      return this.picture_;
    },

    set displayItemList(displayItemList) {
      this.displayItemList_ = displayItemList;
      this.picture = this.displayItemList_;

      this.displayItemListView_.clear();
      this.displayItemList_.items.forEach(function(item) {
        const listItem = document.createElement(
            'tr-ui-e-chrome-cc-display-item-list-item');
        listItem.data = item;
        Polymer.dom(this.displayItemListView_).appendChild(listItem);
      }.bind(this));
    },

    set picture(picture) {
      this.picture_ = picture;

      // Hide the ops list if we are showing the "main" display item list.
      const showOpsList = picture && picture !== this.displayItemList_;
      this.updateDrawOpsList_(showOpsList);

      if (picture) {
        const size = this.getRasterCanvasSize_();
        this.rasterCanvas_.width = size.width;
        this.rasterCanvas_.height = size.height;
      }

      const bounds = this.rasterArea_.getBoundingClientRect();
      const selectorBounds = this.mouseModeSelector_.getBoundingClientRect();
      this.mouseModeSelector_.pos = {
        x: (bounds.right - selectorBounds.width - 10),
        y: bounds.top
      };

      this.rasterize_();

      this.scheduleUpdateContents_();
    },

    getRasterCanvasSize_() {
      const style = window.getComputedStyle(this.rasterArea_);
      let width = parseInt(style.width);
      let height = parseInt(style.height);
      if (this.picture_) {
        width = Math.max(width, this.picture_.layerRect.width);
        height = Math.max(height, this.picture_.layerRect.height);
      }

      return {
        width,
        height
      };
    },

    scheduleUpdateContents_() {
      if (this.updateContentsPending_) return;

      this.updateContentsPending_ = true;
      tr.b.requestAnimationFrameInThisFrameIfPossible(
          this.updateContents_.bind(this)
      );
    },

    updateContents_() {
      this.updateContentsPending_ = false;

      if (this.picture_) {
        Polymer.dom(this.sizeInfo_).textContent = '(' +
            this.picture_.layerRect.width + ' x ' +
            this.picture_.layerRect.height + ')';
      }

      // Return if picture hasn't finished rasterizing.
      if (!this.pictureAsImageData_) return;

      this.infoBar_.visible = false;
      this.infoBar_.removeAllButtons();
      if (this.pictureAsImageData_.error) {
        this.infoBar_.message = 'Cannot rasterize...';
        this.infoBar_.addButton('More info...', function(e) {
          const overlay = new tr.ui.b.Overlay();
          Polymer.dom(overlay).textContent = this.pictureAsImageData_.error;
          overlay.visible = true;
          e.stopPropagation();
          return false;
        }.bind(this));
        this.infoBar_.visible = true;
      }

      this.drawPicture_();
    },

    drawPicture_() {
      const size = this.getRasterCanvasSize_();
      if (size.width !== this.rasterCanvas_.width) {
        this.rasterCanvas_.width = size.width;
      }
      if (size.height !== this.rasterCanvas_.height) {
        this.rasterCanvas_.height = size.height;
      }

      this.rasterCtx_.clearRect(0, 0, size.width, size.height);

      if (!this.picture_ || !this.pictureAsImageData_.imageData) return;

      const imgCanvas = this.pictureAsImageData_.asCanvas();
      const w = imgCanvas.width;
      const h = imgCanvas.height;
      this.rasterCtx_.drawImage(imgCanvas, 0, 0, w, h,
          0, 0, w * this.zoomScaleValue_,
          h * this.zoomScaleValue_);
    },

    rasterize_() {
      if (this.picture_) {
        this.picture_.rasterize(
            {
              showOverdraw: false
            },
            this.onRasterComplete_.bind(this));
      }
    },

    onRasterComplete_(pictureAsImageData) {
      this.pictureAsImageData_ = pictureAsImageData;
      this.scheduleUpdateContents_();
    },

    onDisplayItemListSelection_(e) {
      const selected = this.displayItemListView_.selectedElement;

      if (!selected) {
        this.picture = this.displayItemList_;
        return;
      }

      const index = Array.prototype.indexOf.call(
          this.displayItemListView_.children, selected);
      const displayItem = this.displayItemList_.items[index];
      if (displayItem && displayItem.skp64) {
        this.picture = new tr.e.cc.Picture(
            displayItem.skp64, this.displayItemList_.layerRect);
      } else {
        this.picture = undefined;
      }
    },

    onDisplayItemInfoClick_(e) {
      if (e && e.target === this.displayItemInfo_) {
        this.displayItemListView_.selectedElement = undefined;
      }
    },

    updateDrawOpsList_(showOpsList) {
      if (showOpsList) {
        this.pictureOpsListView_.picture = this.picture_;
        if (this.pictureOpsListView_.numOps > 0) {
          this.pictureOpsListView_.style.display = 'block';
          this.pictureOpsListDragHandle_.style.display = 'block';
        }
      } else {
        this.pictureOpsListView_.style.display = 'none';
        this.pictureOpsListDragHandle_.style.display = 'none';
      }
    },

    trackMouse_() {
      this.mouseModeSelector_ = document.createElement(
          'tr-ui-b-mouse-mode-selector');
      this.mouseModeSelector_.targetElement = this.rasterArea_;
      Polymer.dom(this.rasterArea_).appendChild(this.mouseModeSelector_);

      this.mouseModeSelector_.supportedModeMask =
          tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
      this.mouseModeSelector_.mode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
      this.mouseModeSelector_.defaultMode = tr.ui.b.MOUSE_SELECTOR_MODE.ZOOM;
      this.mouseModeSelector_.settingsKey = 'pictureDebugger.mouseModeSelector';

      this.mouseModeSelector_.addEventListener('beginzoom',
          this.onBeginZoom_.bind(this));
      this.mouseModeSelector_.addEventListener('updatezoom',
          this.onUpdateZoom_.bind(this));
      this.mouseModeSelector_.addEventListener('endzoom',
          this.onEndZoom_.bind(this));
    },

    onBeginZoom_(e) {
      this.isZooming_ = true;

      this.lastMouseViewPos_ = this.extractRelativeMousePosition_(e);

      e.preventDefault();
    },

    onUpdateZoom_(e) {
      if (!this.isZooming_) return;

      const currentMouseViewPos = this.extractRelativeMousePosition_(e);

      // Take the distance the mouse has moved and we want to zoom at about
      // 1/1000th of that speed. 0.01 feels jumpy. This could possibly be tuned
      // more if people feel it's too slow.
      this.zoomScaleValue_ +=
          ((this.lastMouseViewPos_.y - currentMouseViewPos.y) * 0.001);
      this.zoomScaleValue_ = Math.max(this.zoomScaleValue_, 0.1);

      this.drawPicture_();

      this.lastMouseViewPos_ = currentMouseViewPos;
    },

    onEndZoom_(e) {
      this.lastMouseViewPos_ = undefined;
      this.isZooming_ = false;
      e.preventDefault();
    },

    extractRelativeMousePosition_(e) {
      return {
        x: e.clientX - this.rasterArea_.offsetLeft,
        y: e.clientY - this.rasterArea_.offsetTop
      };
    },

    saveFile_(filename, rawData) {
      if (!rawData) return;

      // Convert this String into an Uint8Array
      const length = rawData.length;
      const arrayBuffer = new ArrayBuffer(length);
      const uint8Array = new Uint8Array(arrayBuffer);
      for (let c = 0; c < length; c++) {
        uint8Array[c] = rawData.charCodeAt(c);
      }

      // Create a blob URL from the binary array.
      const blob = new Blob([uint8Array], {type: 'application/octet-binary'});
      const blobUrl = window.URL.createObjectURL(blob);

      // Create a link and click on it.
      const link = document.createElementNS('http://www.w3.org/1999/xhtml', 'a');
      link.href = blobUrl;
      link.download = filename;
      const event = document.createEvent('MouseEvents');
      event.initMouseEvent(
          'click', true, false, window, 0, 0, 0, 0, 0,
          false, false, false, false, 0, null);
      link.dispatchEvent(event);
    },

    onExportDisplayListClicked_() {
      const rawData = JSON.stringify(this.displayItemList_.items);
      this.saveFile_(this.displayListFilename_.value, rawData);
    },

    onExportSkPictureClicked_() {
      const rawData = tr.b.Base64.atob(this.picture_.getBase64SkpData());
      this.saveFile_(this.skpFilename_.value, rawData);
    }
  };

  return {
    DisplayItemDebugger,
  };
});
</script>
